using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Collections.Generic;

namespace EncryptHelper
{
    /// <summary>
    /// using Aes Algorithm
    /// </summary>
    [Serializable]
    public class EncryptHelper<T> where T : SymmetricAlgorithm, new()
    {
        private static byte[] randomBytes = { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76, 0x65, 0x15, 0x25 };

        /// <summary>
        ///  Encrypt a byte array into a byte array using a key and an IV 
        /// </summary>
        public static byte[] Encrypt(byte[] cleanBytes, byte[] Key)
        {
            List<byte> list = new List<byte>();
            MemoryStream msEncrypt = new MemoryStream();
            using (SymmetricAlgorithm alg = new T())
            {
                alg.Key = Key;
                alg.Padding = PaddingMode.PKCS7;
                
                alg.GenerateIV();
                list.AddRange(alg.IV);

                using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, alg.CreateEncryptor(), CryptoStreamMode.Write))
                {
                    csEncrypt.Write(cleanBytes, 0, cleanBytes.Length);
                }
            }
            list.AddRange(msEncrypt.ToArray());
            return list.ToArray();
        }

        /// <summary>
        /// Encrypt bytes into bytes using a password 
        /// </summary>
        public static byte[] Encrypt(byte[] clearbytes, string Password)
        {
            Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(Password, randomBytes);
            return Encrypt(clearbytes, pdb.GetBytes(new T().Key.Length));
        }

        /// <summary>
        /// Encrypt a string into a string using byte [] 
        /// </summary>
        public static string Encrypt(string clearText, byte[] Key)
        {
            byte[] clearBytes = Encoding.UTF8.GetBytes(clearText);
            byte[] encryptedbytes = Encrypt(clearBytes, Key);
            return Convert.ToBase64String(encryptedbytes);
        }


        /// <summary>
        /// Encrypt a string into a string using a password and aes algorithm
        /// </summary>
        public static string Encrypt(string clearText, string Password)
        {
            byte[] clearBytes = Encoding.UTF8.GetBytes(clearText);
            byte[] encryptedbytes = Encrypt(clearBytes, Password);
            return Convert.ToBase64String(encryptedbytes);
        }


        /// <summary>
        /// Encrypt a file into another file using a password 
        /// </summary>
        /// <param name="Password"></param>
        public static void Encrypt(string fileIn, string fileOut, string Password)
        {
            Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(Password, randomBytes);
            EncryptHelper<T>.Encrypt(fileIn, fileOut, pdb.GetBytes(new T().Key.Length));
        }


        /// <summary>
        /// Encrypt a file into another file using a key 
        /// </summary>
        /// <param name="Password"></param>
        public static void Encrypt(string fileIn, string fileOut, byte[] key)
        {
            using (SymmetricAlgorithm alg = new T())
            {
                alg.Key = key;
                alg.Padding = PaddingMode.PKCS7;

                alg.GenerateIV();
                File.WriteAllBytes(fileOut, alg.IV);
                using (FileStream fsIn = new FileStream(fileIn, FileMode.Open, FileAccess.Read,FileShare.Read))
                {
                    using (FileStream fsOut = new FileStream(fileOut, FileMode.Append, FileAccess.Write))
                    {
                        using (CryptoStream cs = new CryptoStream(fsOut, alg.CreateEncryptor(), CryptoStreamMode.Write))
                        {
                            int bufferLen = 10240;
                            byte[] buffer = new byte[bufferLen];
                            int bytesRead;
                            do
                            {
                                // read a chunk of data from the input file 
                                bytesRead = fsIn.Read(buffer, 0, bufferLen);
                                cs.Write(buffer, 0, bytesRead);
                            } while (bytesRead != 0);
                        }
                    }
                }
            }
        }



        /// <summary>
        ///  Decrypt a byte array into a byte array using a key and an IV 
        /// </summary>
        public static byte[] Decrypt(byte[] encryptedBytes, byte[] Key)
        {
            MemoryStream msDecrypt = new MemoryStream();
            using (SymmetricAlgorithm alg = new T())
            {
                alg.Key = Key;
                alg.Padding = PaddingMode.PKCS7;

                byte[] IV = new byte[new T().IV.Length];
                Array.Copy(encryptedBytes, IV, IV.Length);
                byte[] originalEncryptedBytes = new byte[encryptedBytes.Length - new T().IV.Length];
                Array.Copy(encryptedBytes, IV.Length, originalEncryptedBytes, 0, originalEncryptedBytes.Length);
                alg.IV = IV;

                using (CryptoStream csEncrypt = new CryptoStream(msDecrypt, alg.CreateDecryptor(), CryptoStreamMode.Write))
                {
                    csEncrypt.Write(originalEncryptedBytes, 0, originalEncryptedBytes.Length);
                }
            }
            return msDecrypt.ToArray();
        }


        // Decrypt bytes into bytes using a password 
        //    Uses Decrypt(byte[], byte[], byte[]) 
        public static byte[] Decrypt(byte[] cipherBytes, string Password)
        {
            Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(Password, randomBytes);
            return Decrypt(cipherBytes, pdb.GetBytes(new T().Key.Length));
        }

        // Decrypt a string into a string using a byte [] key 
        //    Uses Decrypt(byte[], byte[], byte[]) 
        public static string Decrypt(string cipherText, byte[] key)
        {
            byte[] cipherBytes = Convert.FromBase64String(cipherText);
            byte[] decryptedBytes = Decrypt(cipherBytes, key);
            return Encoding.UTF8.GetString(decryptedBytes);
        }


        // Decrypt a string into a string using a password 
        //    Uses Decrypt(byte[], byte[], byte[]) 
        public static string Decrypt(string cipherText, string Password)
        {
            byte[] cipherBytes = Convert.FromBase64String(cipherText);
            byte[] decryptedBytes = Decrypt(cipherBytes, Password);
            return Encoding.UTF8.GetString(decryptedBytes);
        }

        /// <summary>
        ///  Decrypt a file into another file using a password 
        /// </summary>
        public static void Decrypt(string fileIn, string fileOut, string Password)
        {
            using (SymmetricAlgorithm alg = new T())
            {
                Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(Password, randomBytes);
                alg.Key = pdb.GetBytes(new T().Key.Length);
                alg.Padding = PaddingMode.PKCS7;

                using (FileStream fsIn = new FileStream(fileIn, FileMode.Open, FileAccess.Read,FileShare.Read))
                {
                    byte[] IV = new byte[new T().IV.Length];
                    fsIn.Read(IV, 0, IV.Length);
                    alg.IV = IV;
                    fsIn.Seek(alg.IV.Length, SeekOrigin.Begin);
                    using (FileStream fsOut = new FileStream(fileOut, FileMode.Create, FileAccess.Write))
                    {
                        using (CryptoStream cs = new CryptoStream(fsOut, alg.CreateDecryptor(), CryptoStreamMode.Write))
                        {
                            int bufferLen = 4096;
                            byte[] buffer = new byte[bufferLen];
                            int bytesRead = 0;
                            do
                            {
                                // read a chunk of data from the input file 
                                bytesRead = fsIn.Read(buffer, 0, bufferLen);
                                cs.Write(buffer, 0, bytesRead);
                            } while (bytesRead != 0);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Get random key
        /// </summary>
        public static byte[] GenerateKey()
        {
            T item=new T ();
            item.GenerateKey();
            return item.Key;
        }

    }
}